/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.cdiggins.com
*/

#ifndef CVML_VM_HPP
#define CVML_VM_HPP

struct VM
{
    Stack<Val> stk;	
	Binary* binary;	
	
    VM()
		: binary(NULL)
	{
    }

	VM(Stack<Val>& stack)
		: stk(stack) 
	{
	}

	void Run(Binary* _binary, int n)
	{
		binary = _binary;
		Run(binary->GetFxn(n));
	}

private:

	void Run(Val val)
	{
		Run(binary->GetFxn(val.AsInt()));
	}

    void Run(u1* current)
    {
        Val tmp;
        Val tmp2;

        while (true) 
        {         
            // compute opcode width
            OpCodeEnum opcode = (OpCodeEnum)(*current++);
            u4 operand = 0;            
            switch (GetOpCodeWidth(opcode)) {
				case 0:
                case 1:
                    operand = 0;
                    break;
                case 2:
                    operand |= *current++;
                    break;
                case 3:
                    operand |= *current++ << 8;
                    operand |= *current++;
                    break;
                case 5:
                    operand |= *current++ << 24;
                    operand |= *current++ << 16;
                    operand |= *current++ << 8;
                    operand |= *current++;
                    break;
                default:
                    Error("unexpected opcode width");
            }
            
            switch (opcode)
            {
            case(op_noop) : 
                break;

            // push values onto the stack
			case(op_push_int) : 
            case(op_push_char) : 
            case(op_push_byte) : 
                stk.Push(Val(operand));
                break;
            case(op_push_sub) : 
                stk.Push(Val(operand));
                break;
            case(op_push_neg1) : 
                stk.Push(Val(-1));
                break;
            case(op_push0) : 
                stk.Push(Val(0));
                break;
            case(op_push1) : 
                stk.Push(Val(1));
                break;case(op_push2) : 
                stk.Push(Val(2));
                break;
            case(op_push_true) : 
                stk.Push(Val(true));
                break;
            case(op_push_false) : 
                stk.Push(Val(false));
                break;
            case(op_push_string) : 
				stk.Push(Val(reinterpret_cast<const char*>(binary->GetConst(operand))));
                break;
			case(op_push_short) : 
				stk.Push(Val(operand));
				break;

            // stack manipulation
            case(op_pop3) :
                stk.Pop();
            case(op_pop2) : 
                stk.Pop();
            case(op_pop) : 
                stk.Pop();
                break;
            case(op_popd) :
                stk.Swap();
                stk.Pop();
                break;
            case (op_dup) :
                stk.Push(stk.Top());
                break;
            case (op_dupd) :
                tmp = stk.Pull();
                stk.Push(stk.Top());
                stk.Push(tmp);
                break;
            case (op_dup2) :
                stk.Push(stk.Below(1));
                stk.Push(stk.Below(1));
                break;
            case (op_dup3) :
                stk.Push(stk.Below(2));
                stk.Push(stk.Below(2));
                stk.Push(stk.Below(2));
                break;
            case (op_swap) :
                stk.Swap();
                break;
            case (op_swapd) :
                tmp = stk.Pull();
                stk.Swap();
                stk.Push(tmp);
                break;
            case (op_swap1):
            case (op_swap2):
            case (op_swap3):
            case (op_swapN):
                Error("unimplemented");

			// get and set
            case (op_getN):
                stk.Push(stk.Below(operand));
                break;
            case (op_get1):
                stk.Push(stk.Below(1));
                break;
            case (op_get2):
                stk.Push(stk.Below(2));
                break;
            case (op_get3):
                stk.Push(stk.Below(3));
                break;
            case (op_get4):
                stk.Push(stk.Below(4));
                break;
            case (op_get5):
                stk.Push(stk.Below(5));
                break;
            case (op_get6):
                stk.Push(stk.Below(6));
                break;
            case (op_get7):
                stk.Push(stk.Below(7));
                break;
            case (op_get8):
                stk.Push(stk.Below(8));
                break;
            case (op_setN):
                stk.Below(operand) = stk.Top();
                stk.Pop();
                break;
            case (op_set1):
                stk.Swap();
                stk.Pop();
                break;
            case (op_set2):
                stk.Push(stk.Below(2));
                stk.Pop();
                break;
            case (op_set3):
                stk.Push(stk.Below(3));
                stk.Pop();
                break;
            case (op_set4):
                stk.Push(stk.Below(4));
                stk.Pop();
                break;
            case (op_set5):
                stk.Push(stk.Below(5));
                stk.Pop();
                break;
            case (op_set6):
                stk.Push(stk.Below(6));
                stk.Pop();
                break;
            case (op_set7):
                stk.Push(stk.Below(7));
                stk.Pop();
                break;
            case (op_set8):
                stk.Push(stk.Below(8));
                stk.Pop();
                break;

            // comparisons
            case (op_lt):
            case (op_lt_int):
            case (op_lt_flt):
                tmp = stk.Pull();
                stk.Push(stk.Pull().Lt(tmp));
                break;
            case (op_gt):
            case (op_gt_int):
            case (op_gt_flt):
                tmp = stk.Pull();
                stk.Push(stk.Pull().Gt(tmp));
                break;
            case (op_lteq):
            case (op_lteq_int):
            case (op_lteq_flt):
                tmp = stk.Pull();
                stk.Push(stk.Pull().LtEq(tmp));
                break;
            case (op_gteq):
            case (op_gteq_int):
            case (op_gteq_flt):
                tmp = stk.Pull();
                stk.Push(stk.Pull().GtEq(tmp));
                break;
            case (op_eq):
            case (op_eq_int):
            case (op_eq_flt):
                tmp = stk.Pull();
                stk.Push(stk.Pull().Eq(tmp));
                break;
            case (op_neq):
            case (op_neq_int):
            case (op_neq_flt):
                tmp = stk.Pull();
                stk.Push(stk.Pull().Neq(tmp));
                break;

            // artithmetic
            case (op_add):
            case (op_add_int):
            case (op_add_flt):
                tmp = stk.Pull();
                stk.Push(stk.Pull().Add(tmp));
                break;
            case (op_sub):
            case (op_sub_int):
            case (op_sub_flt):
                tmp = stk.Pull();
                stk.Push(stk.Pull().Sub(tmp));
                break;
            case (op_mul):
            case (op_mul_int):
            case (op_mul_flt):
                tmp = stk.Pull();
                stk.Push(stk.Pull().Mul(tmp));
                break;
            case (op_div):
            case (op_div_int):
            case (op_div_flt):
                tmp = stk.Pull();
                stk.Push(stk.Pull().Div(tmp));
                break;
            case (op_mod_int):
                tmp = stk.Pull();
                stk.Push(stk.Pull().Mod(tmp));
                break;
            case (op_neg):
            case (op_neg_flt):
            case (op_neg_int):
                stk.Push(stk.Pull().Neg());
                break;
            case (op_inc):
                stk.Push(stk.Pull().Add(Val(1)));
                break;
            case (op_dec):
                stk.Push(stk.Pull().Sub(Val(1)));
                break;

            // boolean operations
            case (op_not):
                stk.Push(stk.Pull().Not());
                break;
            case (op_or):
                stk.Push(stk.Pull().Or(stk.Pull()));
                break;
            case (op_and):
                stk.Push(stk.Pull().And(stk.Pull()));
                break;
            case (op_xor):
                stk.Push(stk.Pull().XOr(stk.Pull()));
                break;

            // higher order functions
            case (op_ret):
				// Terminates the function
                return;
            case (op_apply):
                Run(stk.Pull());
                break;
            case (op_tail_apply):
				current = binary->GetFxn(stk.Pull().AsUInt());
                break;
            case (op_dip):
                tmp = stk.Pull();
                tmp2 = stk.Pull();
                Run(tmp);
                stk.Push(tmp2);
                break;

            // control flow (loops and conditional)
            case (op_if):
                tmp = stk.Pull();
                tmp2 = stk.Pull();
				if (stk.Pull().AsBool()) {
                    Run(tmp2);
                }
                else {
                    Run(tmp);
                }
                break;
            case (op_while):
                tmp = stk.Pull();
                tmp2 = stk.Pull();
                Run(tmp2);
                while (stk.Pull().AsBool()) {
                    Run(tmp);
                    Run(tmp2);
                }
                break;

            // objects
            case (op_get_slot) :
                Error("unimplemented");
                break;
            case (op_set_slot) :
                Error("unimplemented");
                break;
            case (op_new) : 
                Error("unimplemented");
                break;        
            case (op_null) :
                Error("unimplemented");
                break;

            // arrays and lists
            case (op_new_array) :
                Error("unimplemented");
                break;
            case (op_count):
                Error("unimplemented");
                break;
            case (op_get_at):
                Error("unimplemented");
                break;
            case (op_set_at):
                Error("unimplemented");
                break;
            case (op_fold):
                Error("unimplemented");
                break;
            case (op_map):
                Error("unimplemented");
                break;
            case (op_filter):
                Error("unimplemented");
                break;
            case (op_filter_map):
                Error("unimplemented");
                break;
            case (op_range):
                Error("unimplemented");
                break;
            case (op_empty):
                Error("unimplemented");
                break;
            case (op_nil):
                Error("unimplemented");
                break;
            case (op_lookup):
                Error("unimplemented");
                break;

            // Indicates that this is an extended byte-code set
            case (op_extended):
                Error("unimplemented");
                break;
            
			default:
                Error("unrecognized op-code");
            }
        }
    }
   
};

#endif
